<?php


namespace XinSheng;

use Fize\Datetime\Date;

/**
 * 外部年利率定价模型
 */
class ModelByOutsideRate
{

    /**
     * @var float 转让价款
     */
    protected $leaseAmount;

    /**
     * @var float 转让价款比例
     */
    protected $leaseAmountRatio;

    /**
     * @var float 首付款
     */
    protected $downPayment;

    /**
     * @var float 首付款比例
     */
    protected $downPaymentRatio;

    /**
     * @var float 预付款
     */
    protected $deposit;

    /**
     * @var float 预付款比例
     */
    protected $depositRatio;

    /**
     * @var float 留购价
     */
    protected $residualValue;

    /**
     * @var float 留购价比例
     */
    protected $residualValueRatio;

    /**
     * @var float 手续费
     */
    protected $serviceAmount;

    /**
     * @var float 手续费比例
     */
    protected $serviceAmountRatio;

    /**
     * @var float 内部年化IRR
     */
    protected $interestRate;

    /**
     * @var float 内部年化IRR2
     */
    protected $interestRate2;

    /**
     * @var int 期次
     */
    protected $issues;

    /**
     * @var array 其他费用
     */
    protected $otherfees;

    /**
     * @var array 其他费用2
     */
    protected $otherfees2;

    /**
     * @var float|null 首次利息本金比
     */
    protected $interestRateFirst;

    /**
     * @var int 每期月数
     */
    protected $issueMonths;

    /**
     * @var int 支付方式：0-期末支付；1-期初支付
     */
    protected $paymentMode;

    /**
     * @var float 每期利率
     */
    protected $issueRate;

    /**
     * @var float 每期利率2
     */
    protected $issueRate2;

    /**
     * @var int 保留小数位
     */
    protected $scale;

    /**
     * @var array 前端计划表
     */
    protected $frontPlan;

    /**
     * @var float 外部年化IRR
     */
    protected $outsideRate;

    /**
     * @var OutsideRateByIrr  ORBI算法对象
     */
    protected $orbi;

    /**
     * 初始化
     * @param float $leaseAmount        转让价款
     * @param float $leaseAmountRatio   转让价款比例
     * @param float $downPayment        首付款
     * @param float $downPaymentRatio   首付款比例
     * @param float $deposit            预付款
     * @param float $depositRatio       预付款比例
     * @param float $residualValue      留购价
     * @param float $residualValueRatio 留购价比例
     * @param float $serviceAmount      手续费
     * @param float $serviceAmountRatio 手续费比例
     * @param float $interestRate2      内部年化IRR2
     * @param int   $issues             期次
     * @param array $otherfees2         其他费用
     * @param int   $paymentMode        支付方式：0-期末支付；1-期初支付
     * @param int   $issueMonths        每期月数
     * @param int   $scale              保留小数位
     */
    public function __construct(
        $leaseAmount, $leaseAmountRatio, $downPayment, $downPaymentRatio, $deposit,
        $depositRatio, $residualValue, $residualValueRatio, $serviceAmount, $serviceAmountRatio,
        $interestRate2, $issues, $otherfees2 = [], $paymentMode = 0, $issueMonths = 1,
        $scale = 2)
    {
        $this->leaseAmount = $leaseAmount;
        $this->leaseAmountRatio = $leaseAmountRatio;
        $this->downPayment = $downPayment;
        $this->downPaymentRatio = $downPaymentRatio;
        $this->deposit = $deposit;
        $this->depositRatio = $depositRatio;
        $this->residualValue = $residualValue;
        $this->residualValueRatio = $residualValueRatio;
        $this->serviceAmount = $serviceAmount;
        $this->serviceAmountRatio = $serviceAmountRatio;
        $this->interestRate2 = $interestRate2;
        $this->issues = $issues;
        $this->otherfees2 = $otherfees2;
        $this->paymentMode = $paymentMode;
        $this->issueMonths = $issueMonths;

        $this->issueRate2 = $this->interestRate2 * $this->issueMonths / 12;
        $this->scale = $scale;

        $this->orbi = new OutsideRateByIrr(
            $this->leaseAmount, $this->leaseAmountRatio, $this->downPayment, $this->downPaymentRatio, $this->deposit,
            $this->depositRatio, $this->residualValue, $this->residualValueRatio, $this->serviceAmount, $this->serviceAmountRatio,
            $this->interestRate2, $this->issues, $this->otherfees2, $this->paymentMode, $this->issueMonths,
            $this->scale
        );
    }

    /**
     * 设置前端
     * @param float      $interestRate      内部年化IRR
     * @param array      $otherfees         其他费用
     * @param float|null $interestRateFirst 首次利息本金比，null表示不指定
     */
    public function setFront($interestRate, $otherfees = [], $interestRateFirst = null)
    {
        $this->orbi->setFront($interestRate, $otherfees, $interestRateFirst);
    }

    /**
     * 设置前端计划表
     * @param array $front_plan
     */
    public function setFrontPlan($front_plan)
    {
        $this->orbi->setFrontPlan($front_plan);
    }

    /**
     * 获取外部年化利率
     * @return float
     */
    public function getOutsideRate()
    {
        if (is_null($this->outsideRate)) {
            $this->outsideRate = $this->orbi->getOutsideRate();
        }
        return $this->outsideRate;
    }

    /**
     * 获取还租计划
     * @param string|null $startDate 起租日，指定起租日则还款计划带有还款日期
     * @return array
     */
    public function getPlan($startDate = null)
    {
        $outsideRate = $this->getOutsideRate();
        $issueOutsideRate = $outsideRate * $this->issueMonths / 12;
        $front_plans = $this->orbi->getFrontPlan();
        $capitals = [];
        $interests = [];
        $balances = [];
        $cashflow = [];
        $balance = round($this->leaseAmount * $this->leaseAmountRatio - $this->downPayment * $this->downPaymentRatio, $this->scale);
        for ($issue = 0; $issue <= $this->issues; $issue++) {
            if ($issue == 0) {
                $capitals[0] = 0.00;
                $interests[0] = 0.00;
                $balances[0] = $balance;
                $cashflow[0] = -round($this->leaseAmount * $this->leaseAmountRatio - $this->downPayment * $this->downPaymentRatio - $this->deposit * $this->depositRatio - $this->serviceAmount * $this->serviceAmountRatio, $this->scale);  // -(转让价款 - 首付款 - 预付款 - 手续费 - 其他费用)
            } else {
                if ($issue == $this->issues) {  // 修正最后一期的本金
                    $capital = $balance;
                } else {
                    $capital = round($front_plans[$issue]['capital'] * $this->leaseAmountRatio, $this->scale);
                }
                $interest = round($balance * $issueOutsideRate, $this->scale);
                $balance = round($balance - $capital, $this->scale);
                $capitals[$issue] = $capital;
                $interests[$issue] = $interest;
                $balances[$issue] = $balance;
                $cashflow[$issue] = $capital + $interest;
            }
        }
        if ($this->paymentMode == 1) {  // 期初支付调整
            for ($i = 0; $i < $this->issues; $i++) {
                if ($i == 0) {
                    $capitals[0] = $capitals[1];
                    $interests[0] = $interests[1];
                    $balances[0] = $balances[1];
                    $cashflow[0] = $cashflow[0] + $cashflow[1];
                } else {
                    $capitals[$i] = $capitals[$i + 1];
                    $interests[$i] = $interests[$i + 1];
                    $balances[$i] = $balances[$i + 1];
                    $cashflow[$i] = $cashflow[$i + 1];
                }
            }
            $capitals[$this->issues] = 0.00;
            $interests[$this->issues] = 0.00;
            $balances[$this->issues] = 0.00;
            $cashflow[$this->issues] = 0.00;
        }

        $plan = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            $plan[$i]['issue'] = $i;
            if (!empty($startDate)) {
                $plan[$i]['payment_date'] = $i == 0 ? $startDate : Date::getAfter($startDate, $i * $this->issueMonths);
            }

            if ($i == 0) {
                $plan[0]['rent'] = $capitals[$i] + $interests[$i];
                $plan[0]['capital'] = $capitals[$i];
                $plan[0]['interest'] = $interests[$i];
                $plan[0]['balance'] = $balances[$i];  // 剩余本金 = 概算本金 - 本金
                $plan[0]['cash_flow'] = $cashflow[$i];  // 现金流 = -(转让价款 - 首付租金 - 预付款 - 租前租金 - 手续费
                $plan[0]['nominal_cash_flow'] = -$balances[$i];  // 名义现金流 = -剩余本金
                $plan[0]['residual_value'] = 0;  // 留购价另算
                $plan[0]['deposit_deduction'] = 0;  // 预付款抵扣另算
                $plan[0]['otherfee'] = 0;  // 其他费用另算
                $plan[0]['payable_amount'] = $cashflow[$i];  // 应付金额 = 转让价款 - 首付款 - 预付款 - 租前租金 - 手续费
            } else {
                $plan[$i]['rent'] = $capitals[$i] + $interests[$i];
                $plan[$i]['capital'] = $capitals[$i];
                $plan[$i]['interest'] = $interests[$i];
                $plan[$i]['balance'] = $balances[$i];  // 剩余本金 = 概算本金 - 本金
                // 以下此时仅算名义现金流
                $plan[$i]['cash_flow'] = $cashflow[$i];  // 现金流 = 租金 - 预付抵扣 + 留购
                $plan[$i]['nominal_cash_flow'] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $plan[$i]['rent'];  // 名义现金流 = 租金 + 留购
                $plan[$i]['residual_value'] = 0;  // 留购价另算
                $plan[$i]['deposit_deduction'] = 0;  // 预付款抵扣另算
                $plan[$i]['otherfee'] = 0;  // 其他费用另算
                $plan[$i]['payable_amount'] = 0;  // 应付金额另算
            }
        }

        // 预付款抵扣
        $start = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
        $deposit = round($this->deposit * $this->depositRatio, $this->issues);
        for ($i = $start; $i > 0; $i--) {
            if ($deposit <= 0) break;
            if ($plan[$i]['cash_flow'] <= $deposit) {
                $plan[$i]['deposit_deduction'] = $plan[$i]['cash_flow'];
                $plan[$i]['cash_flow'] = 0;
            } else {
                $plan[$i]['deposit_deduction'] = $deposit;
                $plan[$i]['cash_flow'] = round($plan[$i]['cash_flow'] - $deposit, $this->scale);
            }
            $deposit = round($deposit - $plan[$i]['deposit_deduction'], $this->scale);
        }
        // 留购价
        $plan[$this->issues]['residual_value'] = round($plan[$this->issues]['residual_value'] + $this->residualValue * $this->residualValueRatio, $this->scale);
        $plan[$this->issues]['cash_flow'] = round($plan[$this->issues]['cash_flow'] + $this->residualValue * $this->residualValueRatio, $this->scale);
        $plan[$this->issues]['nominal_cash_flow'] = round($plan[$this->issues]['nominal_cash_flow'] + $this->residualValue * $this->residualValueRatio, $this->scale);
        // 其他费用
        if (!empty($this->otherfees2)) {
            foreach ($this->otherfees2 as $fee) {
                $plan[$fee['issue']]['cash_flow'] = round($plan[$fee['issue']]['cash_flow'] + $fee['amount'], $this->scale);
                $plan[$fee['issue']]['otherfee'] = round($plan[$fee['issue']]['otherfee'] + $fee['amount'], $this->scale);
            }
        }
        for ($i = 1; $i <= $this->issues; $i++) {
            // 应付金额 = 租金 - 预付款抵扣 + 留购价 + 其他费用
            $plan[$i]['payable_amount'] = round($plan[$i]['rent'] - $plan[$i]['deposit_deduction'] + $plan[$i]['residual_value'] + $plan[$i]['otherfee'], $this->scale);
        }
        // 修正第0期应付金额
        $plan[0]['payable_amount'] = -$plan[0]['cash_flow'];
        return $plan;
    }
}